/*!	 synfigapp/instance.cpp
**	 Instance
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**  Copyright (c) 2011 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include "instance.h"
#include "canvasinterface.h"
#include <iostream>
#include <synfig/context.h>
#include <synfig/canvasfilenaming.h>
#include <synfig/loadcanvas.h>
#include <synfig/savecanvas.h>
#include <synfig/filecontainerzip.h>
#include <synfig/filesystem.h>
#include <synfig/filesystemnative.h>
#include <synfig/filesystemtemporary.h>
#include <synfig/valuenodes/valuenode_add.h>
#include <synfig/valuenodes/valuenode_composite.h>
#include <synfig/valuenodes/valuenode_const.h>
#include <synfig/valuenodes/valuenode_radialcomposite.h>
#include <synfig/valuenodes/valuenode_reference.h>
#include <synfig/valuenodes/valuenode_boneinfluence.h>
#include <synfig/valuenodes/valuenode_greyed.h>
#include <synfig/valuenodes/valuenode_blinecalctangent.h>
#include <synfig/valuenodes/valuenode_blinecalcvertex.h>
#include <synfig/valuenodes/valuenode_blinecalcwidth.h>
#include <synfig/valuenodes/valuenode_wplist.h>
#include <synfig/valuenodes/valuenode_scale.h>
#include <synfig/valuenodes/valuenode_range.h>
#include <synfig/valuenodes/valuenode_integer.h>
#include <synfig/valuenodes/valuenode_real.h>
#include <synfig/valuenodes/valuenode_bonelink.h>
#include <synfig/valuenodes/valuenode_average.h>
#include <synfig/valuenodes/valuenode_weightedaverage.h>
#include <synfig/layers/layer_pastecanvas.h>
#include <synfig/layers/layer_bitmap.h>
#include <synfig/rendering/software/surfacesw.h>
#include <synfig/target_scanline.h>
#include "actions/valuedescexport.h"
#include "actions/layerparamset.h"
#include "actions/layerembed.h"
#include <map>

#include <synfigapp/localization.h>

#include <synfig/importer.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace synfigapp;

static std::map<loose_handle<Canvas>, loose_handle<Instance> > instance_map_;

bool
synfigapp::is_editable(synfig::ValueNode::Handle value_node)
{
    if (ValueNode_Const::Handle::cast_dynamic(value_node)
            || ValueNode_Animated::Handle::cast_dynamic(value_node)
            || ValueNode_Add::Handle::cast_dynamic(value_node)
            || ValueNode_Composite::Handle::cast_dynamic(value_node)
            || ValueNode_RadialComposite::Handle::cast_dynamic(value_node)
            || (ValueNode_Reference::Handle::cast_dynamic(value_node) && !ValueNode_Greyed::Handle::cast_dynamic(value_node))
            || ValueNode_BoneInfluence::Handle::cast_dynamic(value_node)
            || ValueNode_BLineCalcVertex::Handle::cast_dynamic(value_node)
            || ValueNode_BLineCalcTangent::Handle::cast_dynamic(value_node)
            || ValueNode_BLineCalcWidth::Handle::cast_dynamic(value_node)
            || ValueNode_Scale::Handle::cast_dynamic(value_node)
            || ValueNode_Range::Handle::cast_dynamic(value_node)
            || ValueNode_Integer::Handle::cast_dynamic(value_node)
            || ValueNode_Real::Handle::cast_dynamic(value_node)
            || ValueNode_BoneLink::Handle::cast_dynamic(value_node)
            || ValueNode_Average::Handle::cast_dynamic(value_node)
            || ValueNode_WeightedAverage::Handle::cast_dynamic(value_node)
       ) {
        return true;
    }

    return false;
}

etl::handle<Instance>
synfigapp::find_instance(etl::handle<synfig::Canvas> canvas)
{
    if (instance_map_.count(canvas) == 0) {
        return 0;
    }

    return instance_map_[canvas];
}

Instance::Instance(etl::handle<synfig::Canvas> canvas, synfig::FileSystem::Handle container):
    CVSInfo(canvas->get_file_name()),
    canvas_(canvas),
    container_(container)
{
    assert(canvas->is_root());

    unset_selection_manager();

    instance_map_[canvas] = this;
} // END of synfigapp::Instance::Instance()

handle<Instance>
Instance::create(etl::handle<synfig::Canvas> canvas, synfig::FileSystem::Handle container)
{
    // Construct a new instance
    handle<Instance> instance(new Instance(canvas, container));

    return instance;
} // END of synfigapp::Instance::create()

synfig::String
Instance::get_file_name()const
{
    return get_canvas()->get_file_name();
}

void
Instance::set_file_name(const synfig::String &name)
{
    get_canvas()->set_file_name(name);
    CVSInfo::set_file_name(name);
}

Instance::~Instance()
{
    instance_map_.erase(canvas_);

    if (getenv("SYNFIG_DEBUG_DESTRUCTORS")) {
        synfig::info("Instance::~Instance(): Deleted");
    }
}

handle<CanvasInterface>
Instance::find_canvas_interface(synfig::Canvas::Handle canvas)
{
    if (!canvas) {
        return 0;
    }

    while (canvas->is_inline()) {
        canvas = canvas->parent();
    }

    CanvasInterfaceList::iterator iter;

    for (iter = canvas_interface_list().begin(); iter != canvas_interface_list().end(); iter++)
        if ((*iter)->get_canvas() == canvas) {
            return *iter;
        }

    return CanvasInterface::create(this, canvas);
}

bool
Instance::import_external_canvas(Canvas::Handle canvas, std::map<Canvas*, Canvas::Handle> &imported)
{
    etl::handle<CanvasInterface> canvas_interface;

    for (IndependentContext i = canvas->get_independent_context(); *i; i++) {
        etl::handle<Layer_PasteCanvas> paste_canvas = etl::handle<Layer_PasteCanvas>::cast_dynamic(*i);

        if (!paste_canvas) {
            continue;
        }

        Canvas::Handle sub_canvas = paste_canvas->get_sub_canvas();

        if (!sub_canvas) {
            continue;
        }

        if (!sub_canvas->is_root()) {
            continue;
        }

        if (imported.count(sub_canvas.get()) != 0) {
            // link already exported canvas
            Canvas::Handle new_canvas = imported[sub_canvas.get()];

            if (!new_canvas) {
                continue;
            }

            // Action to link canvas
            try {
                Action::Handle action(Action::LayerParamSet::create());

                if (!action) {
                    continue;
                }

                canvas_interface = find_canvas_interface(canvas);
                action->set_param("canvas", canvas);
                action->set_param("canvas_interface", canvas_interface);
                action->set_param("layer", Layer::Handle(paste_canvas));
                action->set_param("param", "canvas");
                action->set_param("new_value", ValueBase(new_canvas));

                if (!action->is_ready()) {
                    continue;
                }

                if (!perform_action(action)) {
                    continue;
                }
            } catch (...) {
                continue;
            }
        } else {
            imported[sub_canvas.get()] = NULL;

            // generate name
            std::string fname = filename_sans_extension(basename(sub_canvas->get_file_name()));
            static const char bad_chars[] = " :#@$^&()*";

            for (std::string::iterator j = fname.begin(); j != fname.end(); j++)
                for (const char *k = bad_chars; *k != 0; k++)
                    if (*j == *k) {
                        *j = '_';
                        break;
                    }

            if (fname.empty()) {
                fname = "canvas";
            }

            if (fname[0] >= '0' && fname[0] <= '9') {
                fname = "_" + fname;
            }

            std::string name;
            bool found = false;

            for (int j = 1; j < 1000; j++) {
                name = j == 1 ? fname : strprintf("%s_%d", fname.c_str(), j);

                if (canvas->value_node_list().count(name) == false) {
                    found = true;

                    for (std::list<Canvas::Handle>::const_iterator iter = canvas->children().begin(); iter != canvas->children().end(); iter++)
                        if (name == (*iter)->get_id()) {
                            found = false;
                            break;
                        }

                    if (found) {
                        break;
                    }
                }
            }

            if (!found) {
                continue;
            }

            // Action to import canvas
            try {
                Action::Handle action(Action::ValueDescExport::create());

                if (!action) {
                    continue;
                }

                canvas_interface = find_canvas_interface(canvas);
                action->set_param("canvas", canvas);
                action->set_param("canvas_interface", canvas_interface);
                action->set_param("value_desc", ValueDesc(Layer::Handle(paste_canvas), std::string("canvas")));
                action->set_param("name", name);

                if (!action->is_ready()) {
                    continue;
                }

                if (!perform_action(action)) {
                    continue;
                }

                std::string warnings;
                imported[sub_canvas.get()] = canvas->find_canvas(name, warnings);
            } catch (...) {
                continue;
            }

            return true;
        }
    }

    for (std::list<Canvas::Handle>::const_iterator i = canvas->children().begin(); i != canvas->children().end(); i++)
        if (import_external_canvas(*i, imported)) {
            return true;
        }

    return false;
}

etl::handle<Action::Group>
Instance::import_external_canvases()
{
    synfigapp::Action::PassiveGrouper group(this, _("Import external canvases"));
    std::map<Canvas*, Canvas::Handle> imported;

    while (import_external_canvas(get_canvas(), imported));

    return group.finish();
}

bool Instance::save_surface(const synfig::rendering::Surface::Handle &surface, const synfig::String &filename)
{
    if (!surface || !surface->is_created()) {
        return false;
    }

    rendering::SurfaceSW::Handle surface_sw = rendering::SurfaceSW::Handle::cast_dynamic(surface);

    if (!surface_sw) {
        surface_sw = new rendering::SurfaceSW(*surface);
    }

    return save_surface(surface_sw->get_surface(), filename);
}

bool Instance::save_surface(const synfig::Surface &surface, const synfig::String &filename)
{
    if (surface.get_h() <= 0 || surface.get_w() <= 0) {
        return false;
    }

    String ext = filename_extension(filename);

    if (ext.empty()) {
        return false;
    }

    ext.erase(0, 1);
    String tmpfile = FileSystemTemporary::generate_system_temporary_filename("surface");

    etl::handle<Target_Scanline> target =
        etl::handle<Target_Scanline>::cast_dynamic(
            Target::create(Target::ext_book()[ext], tmpfile, TargetParam()));

    if (!target) {
        return false;
    }

    bool success = true;

    target->set_canvas(get_canvas());
    RendDesc desc;
    desc.set_w(surface.get_w());
    desc.set_h(surface.get_h());
    desc.set_x_res(1);
    desc.set_y_res(1);
    desc.set_frame_rate(1);
    desc.set_frame(0);
    desc.set_frame_start(0);
    desc.set_frame_end(0);
    target->set_rend_desc(&desc);

    if (success) {
        success = target->add_frame(&surface);
    }

    target = NULL;

    if (success) {
        success = get_canvas()->get_file_system()->directory_create(etl::dirname(filename));
    }

    if (success) {
        success = FileSystem::copy(FileSystemNative::instance(), tmpfile, get_canvas()->get_file_system(), filename);
    }

    FileSystemNative::instance()->file_remove(tmpfile);

    return success;
}

void
Instance::process_filename(const ProcessFilenamesParams &params, const synfig::String &filename, synfig::String &out_filename)
{
    String full_filename = CanvasFileNaming::make_full_filename(params.previous_canvas_filename, filename);
    map<String, String>::const_iterator i = params.processed_files.find(full_filename);

    if (i != params.processed_files.end()) {
        out_filename = i->second;
        return;
    }

    if (params.embed_files) {
        if (CanvasFileNaming::can_embed(filename)
                && !CanvasFileNaming::is_embeded(params.previous_canvas_filename, filename)) {
            String new_filename = CanvasFileNaming::generate_container_filename(get_canvas()->get_identifier().file_system, filename);
            String new_full_filename = CanvasFileNaming::make_full_filename(get_canvas()->get_file_name(), out_filename);

            if (FileSystem::copy(
                        params.previous_canvas_filesystem,
                        full_filename,
                        get_canvas()->get_identifier().file_system,
                        new_full_filename)) {
                out_filename = new_filename;
                params.processed_files[full_filename] = out_filename;
                info("embed file: %s -> %s", filename.c_str(), out_filename.c_str());
                return;
            } else {
                warning("Cannot embed file: %s", filename.c_str());
            }
        }
    }

    out_filename = CanvasFileNaming::make_short_filename(params.canvas->get_file_name(), full_filename);
    params.processed_files[full_filename] = out_filename;
    info("refine filename: %s -> %s", filename.c_str(), out_filename.c_str());
}

void
Instance::process_filenames(const ProcessFilenamesParams &params, const synfig::NodeHandle &node, bool self)
{
    if (!node || params.processed_nodes.count(node)) {
        return;
    }

    params.processed_nodes.insert(node);

    // ValueNodeConst
    if (ValueNode_Const::Handle value_node = ValueNode_Const::Handle::cast_dynamic(node)) {
        // allow to process valuenodes without canvas
        if (value_node->get_parent_canvas()
                && value_node->get_parent_canvas()->get_root() != params.canvas) {
            return;
        }

        ValueBase value = value_node->get_value();

        if (self) {
            if (value.same_type_as(String())) {
                String filename = value.get(String());
                String new_filename;
                process_filename(params, filename, new_filename);

                if (filename != new_filename) {
                    params.processed_valuenodes[value_node] = filename;
                    value_node->set_value(new_filename);
                }

                return;
            }

            warning("Cannot process filename for node: %s", node->get_string().c_str());
        }

        if (value.can_get(Canvas::Handle())) {
            process_filenames(params, value.get(Canvas::Handle()));
        }

        return;
    }

    // ValueNode_Animated
    if (ValueNode_Animated::Handle animated = ValueNode_Animated::Handle::cast_dynamic(node)) {
        const WaypointList &waypoints = animated->waypoint_list();

        for (WaypointList::const_iterator i = waypoints.begin(); i != waypoints.end(); ++i) {
            process_filenames(params, i->get_value_node(), self);
        }

        return;
    }

    // Followed node-types cannot by processed by it self (childs only)
    if (self) {
        warning("Cannot process filename for node: %s", node->get_string().c_str());
    }

    // Canvas
    if (Canvas::Handle canvas = Canvas::Handle::cast_dynamic(node)) {
        if (canvas->get_root() != params.canvas) {
            return;
        }

        // exported values
        if (canvas->is_inline()) {
            const ValueNodeList &list = canvas->value_node_list();

            for (ValueNodeList::const_iterator i = list.begin(); i != list.end(); ++i) {
                process_filenames(params, *i);
            }
        }

        // layers
        for (Canvas::const_iterator i = canvas->begin(); i != canvas->end(); ++i) {
            process_filenames(params, *i);
        }

        return;
    }

    // Layer
    if (Layer::Handle layer = Layer::Handle::cast_dynamic(node)) {
        // skip layers without canvas
        if (!layer->get_canvas()
                || layer->get_canvas()->get_root() != params.canvas) {
            return;
        }

        const ParamVocab vocab = layer->get_param_vocab();
        const Layer::DynamicParamList &dynamic_params = layer->dynamic_param_list();

        for (ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i) {
            Layer::DynamicParamList::const_iterator j = dynamic_params.find(i->get_name());
            ValueNode::Handle value_node = j == dynamic_params.end() ? ValueNode::Handle() : ValueNode::Handle(j->second);

            bool is_filename = i->get_hint() == "filename";

            if (value_node) {
                process_filenames(params, value_node, is_filename);
                continue;
            }

            ValueBase value = layer->get_param(i->get_name());

            if (value.can_get(Canvas::Handle())) {
                process_filenames(params, value.get(Canvas::Handle()));
            }

            if (!is_filename || !value.same_type_as(String())) {
                continue;
            }

            String filename = value.get(String());
            String new_filename;
            process_filename(params, filename, new_filename);

            if (filename != new_filename) {
                params.processed_params[std::make_pair(layer, i->get_name())] = filename;
                layer->set_param(i->get_name(), new_filename);
            }
        }

        return;
    }

    // LinkableValueNode
    if (LinkableValueNode::Handle linkable = LinkableValueNode::Handle::cast_dynamic(node)) {
        // allow to process valuenodes without canvas
        if (linkable->get_parent_canvas()
                && linkable->get_parent_canvas()->get_root() != params.canvas) {
            return;
        }

        const ParamVocab vocab = linkable->get_children_vocab();

        for (ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i) {
            process_filenames(params, ValueNode::Handle(linkable->get_link(i->get_name())), i->get_hint() == "filename");
        }

        return;
    }
}

void
Instance::process_filenames_undo(const ProcessFilenamesParams &params)
{
    // restore layer params
    for (std::map<std::pair<Layer::Handle, String>, String>::const_iterator i = params.processed_params.begin(); i != params.processed_params.end(); ++i) {
        i->first.first->set_param(i->first.second, i->second);
    }

    // restore value nodes
    for (std::map<ValueNode_Const::Handle, String>::const_iterator i = params.processed_valuenodes.begin(); i != params.processed_valuenodes.end(); ++i) {
        i->first->set_value(i->second);
    }
}

void
Instance::find_unsaved_layers(std::vector<synfig::Layer::Handle> &out_layers, const synfig::Canvas::Handle canvas)
{
    for (Canvas::const_iterator i = canvas->begin(); i != canvas->end(); ++i) {
        if (Layer_PasteCanvas::Handle layer_pastecanvas = Layer_PasteCanvas::Handle::cast_dynamic(*i))
            if (Canvas::Handle sub_canvas = layer_pastecanvas->get_sub_canvas()) {
                find_unsaved_layers(out_layers, sub_canvas);
            }

        if (Layer_Bitmap::Handle layer_bitmap = Layer_Bitmap::Handle::cast_dynamic(*i))
            if (layer_bitmap->is_surface_modified()) {
                out_layers.push_back(layer_bitmap);
            }
    }
}

bool
Instance::save()
{
    return save_as(get_canvas()->get_file_name());
}

bool
Instance::save_layer(const synfig::Layer::Handle &layer)
{
    if (Layer_Bitmap::Handle layer_bitmap = Layer_Bitmap::Handle::cast_dynamic(layer)) {
        if (layer_bitmap->is_surface_modified()
                && layer_bitmap->get_param_list().count("filename")) {
            ValueBase value = layer_bitmap->get_param("filename");

            if (value.same_type_as(String())) {
                String filename = value.get(String());

                if (save_surface(layer_bitmap->rendering_surface, filename)) {
                    return true;
                }

                error("Cannot save image: %s", filename.c_str());
                return false;
            }
        }
    }

    error("Don't know how to save layer type: %s", layer->get_name().c_str());
}

void
Instance::save_all_layers()
{
    std::vector<Layer::Handle> layers;
    find_unsaved_layers(layers);

    for (std::vector<Layer::Handle>::const_iterator i = layers.begin(); i != layers.end(); ++i) {
        save_layer(*i);
    }
}

bool
Instance::backup()
{
    if (!get_action_count()) {
        return true;
    }

    bool success = true;
    FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(get_canvas()->get_file_system());

    if (success && !temporary_filesystem) {
        warning("Cannot backup, canvas was not attached to temporary file system: %s", get_file_name().c_str());
        success = false;
        return false;
    }

    // don't save images while backup
    if (success) {
        success = save_canvas(get_canvas()->get_identifier(), get_canvas(), false);
    }

    if (success) {
        success = temporary_filesystem->save_temporary();
    }

    return success;
}

bool
Instance::save_as(const synfig::String &file_name)
{
    Canvas::Handle canvas = get_canvas();

    FileSystem::Identifier previous_canvas_identifier = canvas->get_identifier();
    FileSystem::Handle previous_canvas_filesystem = previous_canvas_identifier.file_system;
    FileSystem::Handle previous_container = get_container();
    String previous_canvas_filename = canvas->get_file_name();

    FileSystem::Identifier new_canvas_identifier = previous_canvas_identifier;
    FileSystem::Handle new_canvas_filesystem = previous_canvas_filesystem;
    FileSystem::Handle new_container = previous_container;
    String new_canvas_filename = file_name;
    bool is_filename_changed = previous_canvas_filename != new_canvas_filename;

    if (is_filename_changed) {
        // new canvas filesystem
        FileSystem::Handle new_container = CanvasFileNaming::make_filesystem_container(new_canvas_filename, 0, true);

        if (!new_container) {
            warning("Cannot create container: %s", new_canvas_filename.c_str());
            return false;
        }

        new_canvas_filesystem = CanvasFileNaming::make_filesystem(new_container);

        if (!new_canvas_filesystem) {
            warning("Cannot create canvas filesysem for: %s", new_canvas_filename.c_str());
            return false;
        }

        // wrap into temporary file system
        if (FileSystemTemporary::Handle previous_temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(previous_canvas_filesystem)) {
            FileSystemTemporary::Handle new_temporary_filesystem = new FileSystemTemporary(
                previous_temporary_filesystem->get_tag(),
                previous_temporary_filesystem->get_temporary_directory(),
                new_canvas_filesystem);
            new_temporary_filesystem->set_meta("filename", new_canvas_filename);
            new_temporary_filesystem->set_meta("as", new_canvas_filename);
            new_temporary_filesystem->set_meta("truncate", "0");
            new_canvas_filesystem = new_temporary_filesystem;
        }

        new_canvas_identifier = new_canvas_filesystem->get_identifier(CanvasFileNaming::project_file(new_canvas_filename));

        // copy embedded files
        if (!FileSystem::copy_recursive(
                    previous_canvas_filesystem,
                    CanvasFileNaming::container_prefix,
                    new_canvas_filesystem,
                    CanvasFileNaming::container_prefix)) {
            new_canvas_filesystem.reset();
            new_container.reset();
            return false;
        }

        // remove previous canvas file
        if (previous_canvas_identifier.filename != new_canvas_identifier.filename) {
            new_canvas_filesystem->file_remove(previous_canvas_identifier.filename);
        }

        // set new canvas filename
        canvas->set_file_name(new_canvas_filename);
        canvas->set_identifier(new_canvas_identifier);
        container_ = new_container;
    }

    // save bitmaps
    save_all_layers();

    // find zip-container
    FileContainerZip::Handle new_container_zip = FileContainerZip::Handle::cast_dynamic(new_container);
    bool embed_files = (bool)new_container_zip;
    bool save_files = true;

    etl::handle<Action::Group> import_external_canvases_action;

    if (embed_files) {
        import_external_canvases_action = import_external_canvases();
    }

    // process filenames
    ProcessFilenamesParams params(
        canvas,
        previous_canvas_filesystem,
        previous_canvas_filename,
        (bool)embed_files,
        save_files);
    process_filenames(params, canvas);

    // save
    bool success = true;

    if (success) {
        success = save_canvas(new_canvas_identifier, canvas, false);
    }

    if (success)
        if (FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(new_canvas_filesystem)) {
            success = temporary_filesystem->save_changes();
        }

    if (success && new_container_zip) {
        success = new_container_zip->save();
    }

    if (success) {
        reset_action_count();
    }

    if (success) {
        signal_saved_();
        signal_filename_changed_();

        if (is_filename_changed) {
            CVSInfo::set_file_name(new_canvas_filename);
        }

        return true;
    }

    // undo
    canvas->set_file_name(previous_canvas_filename);
    canvas->set_identifier(previous_canvas_identifier);
    container_ = previous_container;
    process_filenames_undo(params);
    new_canvas_filesystem.reset();
    new_container.reset();

    if (import_external_canvases_action) {
        import_external_canvases_action->undo();
    }

    return false;
}

bool
Instance::generate_new_name(
    synfig::Layer::Handle layer,
    synfig::Canvas::Handle canvas,
    synfig::FileSystem::Handle file_system,
    synfig::String &out_description,
    synfig::String &out_filename,
    synfig::String &out_filename_param)
{
    out_description.clear();
    out_filename.clear();
    out_filename_param.clear();

    String description = layer->get_description();

    if (description.empty()) {
        description = layer->get_local_name();
    }

    String filename;

    if (layer->get_param_list().count("filename")) {
        ValueBase value = layer->get_param("filename");

        if (value.same_type_as(String())) {
            filename = basename(value.get(String()));
        }
    }

    if (filename.empty()) {
        filename = description;
    }

    if (CanvasFileNaming::filename_extension_lower(filename) != "png") {
        filename += ".png";
    }

    assert(canvas->get_file_system());
    String short_filename = CanvasFileNaming::generate_container_filename(canvas->get_file_system(), filename);
    String full_filename = CanvasFileNaming::make_full_filename(canvas->get_file_name(), short_filename);
    String base = etl::filename_sans_extension(CanvasFileNaming::filename_base(short_filename));

    out_description = base;
    out_filename = full_filename;
    out_filename_param = short_filename;

    return true;
}